package gonat

import (
	"errors"
	"fmt"
	"io"
	"net"
	"time"

	"github.com/fanhai/goupnp"
	"github.com/fanhai/goupnp/httpu"
	"github.com/fanhai/goupnp/soap"
	"github.com/fanhai/goupnp/ssdp"
)

var serviceTypes = []string{
	//"ssdp:all"
	"WANIPConnection:2",
	"WANPPPConnection:2",
	"WANIPConnection:1",
	"WANPPPConnection:1",
}

type FoundResponse struct {
	responses []*httpu.FoundResponse
	st        string
}

// DiscoverDevices  搜索常用服务类型，按照顺序进行搜索找到后返回
func DiscoverDevicesUpnp() (*UpnpNatDevice, error) {
	servertypes := []string{URN_WANPPPConnection_1, URN_WANIPConnection_2, URN_WANIPConnection_1, URN_WANPPPConnection_2}
	hc, hcCleanup, err := httpuClient()

	if err != nil {
		return nil, err
	}
	defer hcCleanup()
	found := make(chan *FoundResponse, len(servertypes))
	//var allMaybeRootDevices []UpnpNatDevice

	for i := range servertypes {
		st := servertypes[i]
		go func() {
			responses, err := ssdp.SSDPRawSearch(hc, string(st), 2, 3)
			if err != nil || len(responses) <= 0 {
				found <- nil
			} else {
				found <- &FoundResponse{responses: responses, st: st}
			}

		}()
	}
	// return the one that responds first.
	// discovery needs to be quick, so we stop caring about
	// any responses after a very short timeout.
	timeout := time.NewTimer(10 * time.Second)
	defer timeout.Stop()
	for range servertypes {
		select {
		case c := <-found:
			if c != nil && len(c.responses) > 0 {
				response := c.responses[0]
				device := &UpnpNatDevice{}
				device.serviceType = response.Header.Get("ST")
				loc, err := response.Location()
				if err != nil {
					device.err = err
					continue
				}

				if addr, ok := (response.Localaddr).(*net.UDPAddr); ok {
					device.LocalAddress = addr.IP.String()
				}
				root, err := goupnp.DeviceByURL(loc)
				if err != nil {
					device.err = err
				}

				de := &(root.Device)

				srvs := de.FindService(c.st)
				if len(srvs) == 0 {
					continue
				}
				device.serviceControlURI = &srvs[0].ControlURL.URL
				device.SOAPClient = soap.NewSOAPClient(srvs[0].ControlURL.URL)
				return device, nil
			}
		case <-timeout.C:
			return nil, errors.New("time out")
		}
	}
	return nil, errors.New("not found")
}

// // DiscoverDevices  搜索常用服务类型，按照顺序进行搜索找到后返回
// func DiscoverDevicesUpnp() (*UpnpNatDevice, error) {
// 	//var allMaybeRootDevices []UpnpNatDevice
// 	for _, st := range []string{URN_WANPPPConnection_1, URN_WANIPConnection_2, URN_WANIPConnection_1, URN_WANPPPConnection_2} {

// 		maybeRootDevices, err := DiscoverDevicesByST(st)
// 		if err != nil {
// 			//fmt.Fprintf(os.Stderr, "Could not discover %s devices: %v\n", st, err)
// 		}
// 		if len(maybeRootDevices) > 0 {
// 			return &maybeRootDevices[0], nil
// 		}
// 		//	allMaybeRootDevices = append(allMaybeRootDevices, maybeRootDevices...)

// 	}
// 	return nil, errors.New("Not found")
// }

// DiscoverDevicesByST ：搜索指定服务类型
func DiscoverDevicesByST(searchTarget string) ([]UpnpNatDevice, error) {
	hc, hcCleanup, err := httpuClient()
	if err != nil {
		return nil, err
	}
	defer hcCleanup()
	responses, err := ssdp.SSDPRawSearch(hc, string(searchTarget), 2, 3)
	if err != nil {
		return nil, err
	}

	devices := make([]UpnpNatDevice, len(responses))
	for i, response := range responses {
		device := &devices[i]
		device.serviceType = response.Header.Get("ST")
		loc, err := response.Location()
		if err != nil {
			device.err = err
			continue
		}

		if addr, ok := (response.Localaddr).(*net.UDPAddr); ok {
			device.LocalAddress = addr.IP.String()
		}
		root, err := goupnp.DeviceByURL(loc)
		if err != nil {
			device.err = err
		}

		de := &(root.Device)

		srvs := de.FindService(searchTarget)
		if len(srvs) == 0 {
			return nil, fmt.Errorf("goupnp: service %q not found within device %q (UDN=%q)",
				searchTarget, de.FriendlyName, de.UDN)
		}
		device.serviceControlURI = &srvs[0].ControlURL.URL
		device.SOAPClient = soap.NewSOAPClient(srvs[0].ControlURL.URL)
	}

	return devices, nil
}

// httpuClient creates a HTTPU client that multiplexes to all multicast-capable
// IPv4 addresses on the host. Returns a function to clean up once the client is
// no longer required.
func httpuClient() (httpu.ClientInterface, func(), error) {
	addrs := localIpaddrs

	closers := make([]io.Closer, 0, len(addrs))
	delegates := make([]httpu.ClientInterface, 0, len(addrs))
	for _, addr := range addrs {
		c, err := httpu.NewHTTPUClientAddr(addr.String())
		if err != nil {
			return nil, nil, ctxErrorf(err,
				"creating HTTPU client for address %s", addr)
		}
		closers = append(closers, c)
		delegates = append(delegates, c)
	}

	closer := func() {
		for _, c := range closers {
			c.Close()
		}
	}

	return httpu.NewMultiClient(delegates), closer, nil
}
